
interface  TotpData  {
  issuer: string;
  accountName: string;
  secret: string;
  algorithm: string;
  digits: number; 
  period: number;
  description: string;
}
export class TotpItem {
  data: TotpData;

  static from(issuer: string, accountName: string, secret: string, description: string = ''): TotpItem {
    return new TotpItem({
      issuer: issuer,
      accountName: accountName,
      secret: secret,
      algorithm: 'SHA1',
      digits: 6,
      period: 30,
      description: description,
    });
  }
  static fromAll(issuer: string, accountName: string, secret: string, description: string = '', algorithm: string = 'SHA1', digits:number = 6, period: number = 30) {
    return new TotpItem({
      issuer: issuer,
      accountName: accountName,
      secret: secret,
      algorithm: algorithm,
      digits: digits,
      period: period,
      description: description,
    })
  }

  constructor(data: TotpData){
    this.data = data
  }



  key(): string {
    let str = ''
    if(this.data.issuer !== undefined && this.data.issuer.length > 0){
      str += `${this.data.issuer}`
    }
    str += ":"
    if(this.data.accountName !== undefined && this.data.accountName.length > 0){
      str += `${this.data.accountName}`
    }
    return str;
  }


  toString(): string {
    let str = "optauth://totp"
    if(this.data.issuer !== undefined && this.data.issuer.length > 0){
      str += `/${this.data.issuer}`
    }
    if(this.data.accountName !== undefined && this.data.accountName.length > 0){
      str += `:${this.data.accountName}`
    }
    function pair(option: {isFirst: boolean}, k, v): string { 
      const p = (option.isFirst ? '?' : '&') + `${k}=${v}`;
      option.isFirst = false;
      return p;
    }

    let option: {isFirst:boolean} = { isFirst: true};
    if(this.data.secret !== undefined && this.data.secret.length > 0) {
      str += pair(option, "secret", this.data.secret);
    }
    if(this.data.issuer !== undefined && this.data.issuer.length > 0){
      str += pair(option, "issuer", this.data.issuer);
    }
    if(this.data.algorithm != 'SHA1'){
      str += pair(option, "algorithm", this.data.algorithm)
    }
    if(this.data.digits != 6 && this.data.digits > 0){
      str += pair(option, "digits", this.data.digits);
    }
    if(this.data.period != 30 && this.data.period > 0){
      str += pair(option, "period", this.data.period)
    }
    return str
  }
}

type Pair<K, V> = [K, V];
export interface ITotpInfo extends Iterable<Pair<string, TotpItem>>{
  toString(): string ;
  setItem(item: TotpItem): void ;

  // Map interface 
  set(k: string, v: TotpItem): this ;
  get(k: string): TotpItem  | undefined ;
  has(k: string): boolean ;
  delete(k: string): boolean ;
  clear(): void ;
  keys():IterableIterator<string> ;
  values(): IterableIterator<TotpItem> ;
  entries(): IterableIterator<Pair<string, TotpItem>> ;
  get size():number ;
  //forEach(...)
}

class TotpInfoIterator implements Iterator<Pair<string, TotpItem>>{
  entries: IterableIterator<Pair<string, TotpItem>>
  constructor(data: IterableIterator<Pair<string, TotpItem>>){
    this.entries = data; 
  }

  next(): IteratorResult<Pair<string, TotpItem>> {
    return this.entries.next()
  }
}
class TotpInfo implements ITotpInfo {
  items: Map<string, TotpItem>;

  constructor() {
    this.items = new Map<string, TotpItem>();
  }
  setItem(item: TotpItem): void {
    this.set(item.key(), item);
  }
  toString(): string {
    let str = ""
    for( const [key, value] of this.items){
      str += `[${key}]=${value}`
    }
    return str
  }
  
  set(k: string, v: TotpItem): this {
    this.items.set(k, v); 
    return this;
  }
  get(k: string): TotpItem  | undefined {
    return this.items.get(k);
  }
  has(k: string): boolean {
    return this.items.has(k);
  }
  delete(k: string): boolean{
    return this.items.delete(k);
  }
  clear(): void {
    this.items.clear();
  }
  keys():IterableIterator<string> {
    return this.items.keys();
  }
  values(): IterableIterator<TotpItem> {
    return this.items.values();
  }
  entries(): IterableIterator<Pair<string, TotpItem>> {
    return this.items.entries();
  }
  get size():number {
    return this.items.size;
  }
  [Symbol.iterator](): Iterator<Pair<string, TotpItem>> {
    return new TotpInfoIterator(this.items.entries());
  }
}

export function createTotp(): ITotpInfo {
  return new TotpInfo();
}

export function createTotpTest(): ITotpInfo {
  let r = new TotpInfo();
  r.setItem(TotpItem.from('App', 'user', 'HLVC57OPWSOXGWI6UEP4ILSLSO7PX3CY'));
  r.setItem(TotpItem.from('cstool', 'chenglun', 'HLVC57OPWSOXGWI6UEP4ILSLSO7PX3CY', 'test-label'));
  return r;
}
